b4d27d287a | ||
---|---|---|
.. | ||
components | ||
foundations | ||
README.md | ||
core.cljs | ||
core_spec.cljs | ||
theme.cljs |
README.md
Quo Library
Quo is the name of our mobile component library that implements the Status Design System for Mobile.
The overarching goals of having this component library are:
- Achieve the highest possible fidelity between code and the design system in Figma.
- Decouple components from ever-changing business requirements.
[!NOTE] This document captures our current practices and guidelines for implementing the design system. For guidelines that apply across the entire project, take a look at new-guidelines.
Directory structure and file names
We follow one basic rule: mirror Figma pages and their component names in the directory structure.
For example, in the screenshot below we see the Figma page is Banners
and the component name is Banner
.
Therefore, the structure should look like:
quo/
└── components/
└── banners/
└── banner/
├── component_spec.cljs
├── style.cljs
└── view.cljs
Files view.cljs
, style.cljs
, and component_spec.cljs
should always have
the same name, regardless of component.
Component API
Adhere to the same component properties and values used in a Figma component when translating it to Clojure. This means using the same names for props and the same values. If the Figma property is a boolean, use a question mark suffix to make the name more idiomatic in Clojure.
We have found over time that the less we drift from the design system the better. Some key benefits:
- It helps developers quickly check for issues when comparing the code with the source of truth in Figma.
- It is easier for pull-request reviewers to double-check components for correctness.
- It helps developers create preview screens that are identical or very similar to Figma, which aids in spotting bugs more easily.
- It helps designers review all component variations in preview screens.
In the image above we can see the properties are Type
, State
, Size
,
Icon
, Theme
, and Background
. Translated to Clojure:
;; ns quo.components.buttons.button.view
(def view
[{:keys [type state size icon theme background]}]
...)
Handling Sizes
In the designs, sizes are referred to as integers. To avoid having the codebase littered with magic numbers we instead have a keyword convention to use in components to map these keywords with their sizes.
The convention is :size-<number>
, e.g size 20
is :size-20
;; bad
(defn button
[{:keys [size]}]
[rn/view
{:style {:height (case size
20 20
40 40
0)}}]
...)
;; good
(defn button
[{:keys [size]}]
[rn/view
{:style {:height (case size
:size-20 20
:size-40 40
0)}}]
...)
Clojure var conventions
- Due to the fact that every
view
namespace should export only one component and to avoid the redundancy of[some-component/some-component ...]
, name the public varview
as well. - Try to make all other vars private because they should almost never be used directly.
Component tests
We don't attempt to write component tests verifying how components look on the screen. Instead, we have found a middle ground, where the focus is on verifying if events are triggered as intended and that all component variations are rendered. We use React Native Testing Library.
There are dozens of examples in the repository, so use them as a reference. A good and complete example is quo.components.avatars.user-avatar.component-spec
No-props test
When writing tests for the component that has props, please add one test that covers situation when props aren't passed. Because even if component not showing anything meaningful without props, it shouldn't crash.
(h/describe "Transaction Progress"
(h/test "component renders without props"
(h/render [quo/transaction-progress {}])
(h/is-truthy (h/get-by-label-text :transaction-progress)))
Do not couple the library with re-frame
Don't use re-frame inside this library (e.g. dispatch & subscribe). If a
component needs to be stateful, the state should be local to its rendering
lifecycle (using reagent.core/atom
). Additionally, if the component requires
any other data, it should be passed as arguments.
;; bad
(defn view []
(let [window-width (rf/sub [:dimensions/window-width])]
[rn/pressable {:on-press #(rf/dispatch [:do-xyz])}
(do-something window-width)]))
;; good
(defn view [{:keys [window-width on-press]}]
[rn/pressable {:on-press on-press}
(do-something window-width)])
Themes
Our goal is to make all design system components themeable, which means they should not use, nor fallback to the OS theme, because themes are contextual and can be overridden in specific parts of the app.
To achieve this, use the higher-order function quo.theme/with-theme
to
automatically inject the current theme context (based on the React Context
API).
Use the following pattern:
(ns quo.components.<figma page>.<component name>.view
(:require [quo.theme :as quo.theme]))
(defn- view-internal [{:keys [theme]}]
...)
(def view (quo.theme/with-theme view-internal))
Then pass the theme
value down to all functions that may rely on the OS theme,
like quo.foundations.colors/theme-colors
or quo.foundations.shadows/get
.
Avoid using quo's version number in namespace aliases
When requiring quo namespaces, don't use the version number in the alias, unless for a special reason you need to require both the old and new namespaces in the same file.
[!NOTE] Keep in mind that, at the moment, we need to keep both
src/quo/
andsrc/quo/
directories in the repository, but eventually the old one will go away and the version number will lose its meaning.
;; bad
(ns ...
(require [quo.theme :as quo.theme]
[quo.core :as quo]))
;; good
(ns ...
(require [quo.theme :as quo.theme]
[quo.core :as quo]))
Preview screens
Every component should be accompanied by a preview screen in
src/status_im/contexts/quo_preview/
. Ideally, all possible variations in
Figma should be achievable in the preview screen by changing the input values
without resorting to code changes. Designers will also use this capability to
review components in PR builds.
Allow outermost containers to have their styles overridden
If a component needs to be wrapped in a rn/view
instance to force it to be
styled differently, consider changing the component to accept a
container-style
argument. This will help reduce the number of nodes to be
rendered.
;; bad
[rn/view {:style {:margin-right 12}}
[quo/button
{:size 32}
:i/info]]
;; good
[quo/button
{:size 32
:container-style {:margin-right 12}}
:i/info]