Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate support for SVG icons and fix clear icon #15691

Merged
merged 11 commits into from
Apr 24, 2023
1 change: 1 addition & 0 deletions src/mocks/js_dependencies.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ globalThis.__STATUS_MOBILE_JS_IDENTITY_PROXY__ = new Proxy({}, {get() { return (
{:ClipPath #js {:render identity}
:Circle #js {:render identity}
:Defs #js {:render identity}
:G #js {:render identity}
:Path #js {:render identity}
:Rect #js {:render identity}
:SvgUri #js {:render identity}
Expand Down
40 changes: 27 additions & 13 deletions src/quo2/components/icon.cljs
Original file line number Diff line number Diff line change
@@ -1,29 +1,43 @@
(ns quo2.components.icon
(:require [clojure.string :as string]
[quo2.components.icons.icons :as icons]
[quo2.components.icons.svg :as icons.svg]
[quo2.foundations.colors :as colors]
[react-native.core :as rn]))

(defn- valid-color?
[color]
(or (keyword? color)
(and (string? color)
(not (string/blank? color)))))

(defn memo-icon-fn
([icon-name] (memo-icon-fn icon-name nil))
([icon-name
{:keys [color container-style size
accessibility-label no-color]
{:keys [color color-2 no-color
container-style size accessibility-label]
:or {accessibility-label :icon}}]
(let [size (or size 20)]
^{:key icon-name}
[rn/image
{:style
(merge {:width size
:height size}
(if-let [svg-icon (icons.svg/get-icon icon-name size)]
[svg-icon
{:size size
:color (when (valid-color? color) color)
:color-2 (when (valid-color? color-2) color-2)
:accessibility-label accessibility-label
:style container-style}]
[rn/image
{:style
(merge {:width size
:height size}

(when (not no-color)
{:tint-color (if (and (string? color) (not (string/blank? color)))
color
(colors/theme-colors colors/neutral-100 colors/white))})
(when (not no-color)
{:tint-color (if (and (string? color) (not (string/blank? color)))
color
(colors/theme-colors colors/neutral-100 colors/white))})

container-style)
:accessibility-label accessibility-label
:source (icons/icon-source (str (name icon-name) size))}])))
container-style)
:accessibility-label accessibility-label
:source (icons/icon-source (str (name icon-name) size))}]))))

(def icon (memoize memo-icon-fn))
8 changes: 4 additions & 4 deletions src/quo2/components/icons/icons.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
(:require [clojure.java.io :as io]
[clojure.string :as string]))

(def icon-path "./resources/images/icons2/")
(def ^:private icon-path "./resources/images/icons2/")

(defn require-icon
(defn- require-icon
[size path]
(fn [el]
(let [s (str "." path el ".png")
Expand All @@ -15,15 +15,15 @@
(str size))]
[k `(js/require ~s)])))

(defn get-files
(defn- get-files
[path]
(->> (io/file path)
file-seq
(filter #(string/ends-with? % "png"))
(map #(first (string/split (.getName %) #"@")))
distinct))

(defn get-icons
(defn- get-icons
[size]
(let [path (str icon-path size "x" size "/")]
(into {} (map (require-icon size path) (get-files path)))))
Expand Down
2 changes: 1 addition & 1 deletion src/quo2/components/icons/icons.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
(:require-macros [quo2.components.icons.icons :as icons])
(:require [taoensso.timbre :as log]))

(def icons (icons/resolve-icons))
(def ^:private icons (icons/resolve-icons))

(defn icon-source
[icon]
Expand Down
48 changes: 48 additions & 0 deletions src/quo2/components/icons/svg.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
(ns quo2.components.icons.svg
"Declare icons in this namespace when they have two possible colors, because the
ReactNative `:tint-color` prop affects all non-transparent pixels of PNGs. If
the icon has only one color, prefer a PNG.

Keep all SVG components private and expose them by name in the `icons` var."
(:require [react-native.svg :as svg]
[quo2.foundations.colors :as colors]))

(defn- container
[{:keys [size accessibility-label style]
:or {size 20}}
& children]
(into [svg/svg
{:accessibility-label accessibility-label
:style style
:width size
:height size
:view-box (str "0 0 " size " " size)
:fill :none}]
children))

(defn- clear-20
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to use hardcoded size here? Is it better to have a size options like in the container above?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ajayesivan, the SVG exported by Figma only works correctly with a specific dimension, 20 in this case. The SVG generated by Figma for size 12 is different and I checked other icons and it's the same story. So I chose to be as explicit as possible and name the var with the size. The only thing that was safe to generalize was the SVG viewbox, which I extracted into the container component.

Naming the var with the size was the way I found to tell other devs that this icon var should not be used for other sizes.

It is in fact possible to generalize the icons to different sizes by manipulating the path.d prop, but I felt this was out of scope for this issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's also good that all of these vars are private, and we can safely improve them in the future without causing breakage.

Copy link
Contributor

@ajayesivan ajayesivan Apr 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, we should be able to use a single SVG for all size variations, and I think thats one of the major advantages of using SVG over PNG. Maybe we can discuss this with design team and come up with a better solution.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with @ajayesivan, I think that the point of using vectorized icons is to use one resource for all sizes without loosing quality. Ideally we should export it once.

Also wondering if we add lots of icons how big this file will end up being.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally agree with you guys. If we want to replace PNGs with SVGs in the future, we'll need to coordinate with designers a bit of work, especially to optimize them for reusability for different sizes. The exported SVGs from Figma are fine, but not ideal.

Copy link
Member

@flexsurfer flexsurfer Apr 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would suggest to move svg icons to a separate folder as js files with component, because no need in having hiccup , yes they are small, but still why, there are also tools for exporting svg as react components etc, it should make life easier later , on other hand we could have a macro in cljs and load svg file as we did this before, in that case we don't need to do any manuall manipulations with svg, just export same as png

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, but I think this is a bit of a next step @flexsurfer. Here in this issue my intention was not to create something that can evolve to replace all PNGs. This would be much bigger in scope and more risky as we discussed, since SVG icons may not work that well in all devices.

After this PR, my hope is that we'll explore SVG icons even more, and hopefully manipulating via hiccup won't be necessary as you said. Hopefully we'll be able to automate the work, just as we did with PNGs :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are also tools for exporting svg as react components

I did use a tool to convert the SVG to hiccup automatically ;) Took me 1min http://html2hiccup.buttercloud.com/

i would suggest to move svg icons to a separate folder as js files with component, because no need in having hiccup , yes they are small, but still why, there are also tools for exporting svg as react components etc, it should make life easier later

If you don't mind too much, I prefer to keep the code as is, since there's only one icon now. If this grows, then sure, let's do something better and throw away the code I wrote in this PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes sure, thank you

[{:keys [color color-2] :as props}]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

color 2 is like a background color? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I initially tried to name color-2 as background-color, just to realize it was more confusing for some icons, like mutual-contact, positive-state, etc, especially when/if we migrate more icons to SVGs I think color-2 is more future proof. It's really like a secondary color, but I didn't want to name it secondary-color or alternate-color, etc, so I ended up going for this simplistic name.

(let [color (or color colors/neutral-100)
color-2 (or color-2 colors/white)]
[container props
[svg/path
{:d
"M3 10C3 6.13401 6.13401 3 10 3C13.866 3 17 6.13401 17 10C17 13.866 13.866 17 10 17C6.13401 17 3 13.866 3 10Z"
:fill color}]
[svg/path
{:d
"M9.15142 9.99998L7.07566 12.0757L7.9242 12.9243L9.99994 10.8485L12.0757 12.9242L12.9242 12.0757L10.8485 9.99998L12.9242 7.92421L12.0757 7.07568L9.99994 9.15145L7.92421 7.07572L7.07568 7.92425L9.15142 9.99998Z"
:fill color-2}]]))

(def ^:private icons
{:i/clear-20 clear-20})

(defn- append-to-keyword
[k & xs]
(keyword (apply str
(subs (str k) 1)
xs)))

(defn get-icon
[icon-name size]
(get icons (append-to-keyword icon-name "-" size)))
2 changes: 1 addition & 1 deletion src/quo2/components/inputs/search_input/style.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
[blur? override-theme]
(if blur?
(colors/theme-colors colors/neutral-80-opa-30 colors/white-opa-10 override-theme)
(colors/theme-colors colors/neutral-40 colors/neutral-50 override-theme)))
(colors/theme-colors colors/neutral-40 colors/neutral-60 override-theme)))

(defn cursor
[customization-color override-theme]
Expand Down
4 changes: 0 additions & 4 deletions src/quo2/components/links/url_preview/style.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,6 @@
{:text-transform :lowercase
:color (colors/theme-colors colors/neutral-50 colors/neutral-40)})

(def clear-button
{:border-color colors/danger-50
:border-width 1})

(def clear-button-container
{:width 20
:height 20
Expand Down
5 changes: 2 additions & 3 deletions src/quo2/components/links/url_preview/view.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,8 @@
:hit-slop {:top 3 :right 3 :bottom 3 :left 3}
:accessibility-label :button-clear-preview}
[icon/icon :i/clear
{:size 20
:container-style style/clear-button
:color (colors/theme-colors colors/neutral-50 colors/neutral-60)}]])
{:size 20
:color (colors/theme-colors colors/neutral-50 colors/neutral-60)}]])

(defn view
[{:keys [title body logo on-clear loading? loading-message container-style]}]
Expand Down
1 change: 1 addition & 0 deletions src/react_native/svg.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@
(def defs (reagent/adapt-react-class Svg/Defs))
(def circle (reagent/adapt-react-class Svg/Circle))
(def svgxml (reagent/adapt-react-class Svg/SvgXml))
(def g (reagent/adapt-react-class Svg/G))
15 changes: 4 additions & 11 deletions src/status_im/ui2/screens/chat/composer/images/style.cljs
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
(ns status-im.ui2.screens.chat.composer.images.style
(:require [quo2.foundations.colors :as colors]))
(ns status-im.ui2.screens.chat.composer.images.style)

(def remove-photo-container
{:width 14
:height 14
:border-radius 7
:background-color colors/neutral-50
:position :absolute
:top 5
:right 5
:justify-content :center
:align-items :center})
{:position :absolute
:top 5
:right 5})

(def small-image
{:width 56
Expand Down
4 changes: 3 additions & 1 deletion src/status_im/ui2/screens/chat/composer/images/view.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
:left 5
:top 10
:bottom 10}}
[quo/icon :i/close {:color colors/white :size 12}]]])
[quo/icon :i/clear
{:size 20
:color (colors/theme-colors colors/neutral-50 colors/neutral-60)}]]])

(defn images-list
[images]
Expand Down