Add Quo component library base

Add text component

Add header component

Change animated root ns

Add spacing design tokens

Add components preview

Add Readme

Add safe area components

Use reanimated from component library

Add colors tokens

Add inherit style for text

Add animated header component

A view with header which can be animated on scroll, used in walled, profile and contact screens

Persist previews navigation state

Rename component library into Quo

Document colors

Extend readme

Header handle long titles

Add InterStatus as monospace font

Signed-off-by: Gheorghe Pinzaru <feross95@gmail.com>
This commit is contained in:
Gheorghe Pinzaru 2020-04-10 15:48:41 +03:00
parent 0a4547e652
commit 38125173cb
No known key found for this signature in database
GPG Key ID: C9A094959935A952
26 changed files with 722 additions and 85 deletions

View File

@ -90,6 +90,7 @@
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; };
@ -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 */,

Binary file not shown.

49
src/quo/README.md Normal file
View File

@ -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.

View File

@ -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)

View File

@ -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]))

View File

@ -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}]]]]]]))))

View File

@ -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)]))])

View File

@ -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))))

13
src/quo/core.cljs Normal file
View File

@ -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)

View File

@ -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))

View File

@ -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))

View File

@ -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"})

View File

@ -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")})

7
src/quo/platform.cljs Normal file
View File

@ -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"))

View File

@ -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}]])])

View File

@ -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)))

View File

@ -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)]])])

7
src/quo/react.cljs Normal file
View File

@ -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")))

15
src/quo/react_native.cljs Normal file
View File

@ -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")))

7
src/quo/theme.cljs Normal file
View File

@ -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)))

View File

@ -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))))
(quo-theme/set-theme :dark)
(colors/set-theme :dark))))

View File

@ -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 {})))
(multiaccounts.update/multiaccount-update :appearance theme {})))

View File

@ -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

View File

@ -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)

View File

@ -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}))