Fixes for the Community Channel avatar component (#16379)
- Community channels may not have an emoji, and in such cases, it should fall back to a single letter. The component was recently updated in Figma after this concern was brought up to designers. - The default (fallback) color to be used in the list of community channels should be the community color, not a default "primary-50" as was before. It's a subtle change because the colors are rendered at 10% transparency. - Rewrote the channel-avatar component to follow guidelines. Fixes: - https://github.com/status-im/status-mobile/issues/16332 - https://github.com/status-im/status-mobile/issues/16327
This commit is contained in:
parent
d76e64b1bb
commit
17f5ad79f4
|
@ -1,50 +0,0 @@
|
|||
(ns quo2.components.avatars.channel-avatar
|
||||
(:require [quo2.components.icon :as icons]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[quo2.theme :as theme]
|
||||
[react-native.core :as rn]))
|
||||
|
||||
(defn channel-avatar
|
||||
[{:keys [big? locked? emoji-background-color emoji]}]
|
||||
(let [lock-exists? locked?
|
||||
dark? (theme/dark?)]
|
||||
[rn/view
|
||||
{:style {:width (if big? 32 24)
|
||||
:height (if big? 32 24)
|
||||
:border-radius (if big? 32 24)
|
||||
:justify-content :center
|
||||
:align-items :center
|
||||
:background-color emoji-background-color}}
|
||||
[rn/view
|
||||
{:style {:display :flex
|
||||
:justify-content :center
|
||||
:align-items :center}}
|
||||
[text/text
|
||||
{:size (if big?
|
||||
:paragraph-1
|
||||
:label)} emoji]
|
||||
(when lock-exists?
|
||||
[rn/view
|
||||
{:style {:position :absolute
|
||||
:left (if big?
|
||||
14
|
||||
8)
|
||||
:top (if big?
|
||||
15
|
||||
8)
|
||||
:background-color (if dark?
|
||||
colors/neutral-90
|
||||
colors/white)
|
||||
:border-radius 15
|
||||
:padding 2}}
|
||||
[icons/icon
|
||||
(if locked?
|
||||
:main-icons/locked
|
||||
:main-icons/unlocked)
|
||||
{:color (if dark?
|
||||
colors/neutral-40
|
||||
colors/neutral-50)
|
||||
:container-style {:width 12
|
||||
:height 12}
|
||||
:size 12}]])]]))
|
|
@ -0,0 +1,35 @@
|
|||
(ns quo2.components.avatars.channel-avatar.component-spec
|
||||
(:require [quo2.components.avatars.channel-avatar.view :as component]
|
||||
[test-helpers.component :as h]))
|
||||
|
||||
(h/describe "Channel Avatar"
|
||||
(h/test "default render"
|
||||
(h/render [component/view])
|
||||
(h/is-truthy (h/query-by-label-text :initials))
|
||||
(h/is-null (h/query-by-label-text :emoji))
|
||||
(h/is-null (h/query-by-label-text :lock)))
|
||||
|
||||
(h/test "with emoji, no lock set, large size"
|
||||
(let [emoji "🍓"]
|
||||
(h/render [component/view {:emoji emoji :size :size/l}])
|
||||
(h/is-null (h/query-by-label-text :initials))
|
||||
(h/is-truthy (h/query-by-text emoji))
|
||||
(h/is-null (h/query-by-label-text :lock))))
|
||||
|
||||
(h/test "locked"
|
||||
(h/render [component/view {:locked? true}])
|
||||
(h/is-truthy (h/query-by-label-text :lock)))
|
||||
|
||||
(h/test "unlocked"
|
||||
(h/render [component/view {:locked? false}])
|
||||
(h/is-truthy (h/query-by-label-text :lock)))
|
||||
|
||||
(h/test "no emoji, smaller size"
|
||||
(h/render [component/view {:full-name "Status Mobile"}])
|
||||
(h/is-truthy (h/query-by-text "S")))
|
||||
|
||||
(h/test "no emoji, large size"
|
||||
(h/render [component/view
|
||||
{:full-name "Status Mobile"
|
||||
:size :size/l}])
|
||||
(h/is-truthy (h/query-by-text "SM"))))
|
|
@ -0,0 +1,28 @@
|
|||
(ns quo2.components.avatars.channel-avatar.style
|
||||
(:require [quo2.foundations.colors :as colors]))
|
||||
|
||||
(def lock-icon-size 12)
|
||||
|
||||
(defn outer-container
|
||||
[{:keys [size color]}]
|
||||
(let [size (if (= size :size/l) 32 24)]
|
||||
{:width size
|
||||
:height size
|
||||
:border-radius size
|
||||
:justify-content :center
|
||||
:align-items :center
|
||||
:background-color (colors/theme-alpha color 0.1 0.1)}))
|
||||
|
||||
(defn lock-container
|
||||
[size]
|
||||
(let [distance (if (= size :size/l) 20 12)]
|
||||
{:position :absolute
|
||||
:left distance
|
||||
:top distance
|
||||
:background-color (colors/theme-colors colors/white colors/neutral-95)
|
||||
:border-radius (* 2 lock-icon-size)
|
||||
:padding 2}))
|
||||
|
||||
(def lock-icon
|
||||
{:width lock-icon-size
|
||||
:height lock-icon-size})
|
|
@ -0,0 +1,60 @@
|
|||
(ns quo2.components.avatars.channel-avatar.view
|
||||
(:require [clojure.string :as string]
|
||||
[quo2.components.avatars.channel-avatar.style :as style]
|
||||
[quo2.components.icon :as icons]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[react-native.core :as rn]
|
||||
utils.string))
|
||||
|
||||
(defn- initials
|
||||
[full-name size color]
|
||||
(let [amount-initials (if (= size :size/l) 2 1)]
|
||||
[text/text
|
||||
{:accessibility-label :initials
|
||||
:size :paragraph-2
|
||||
:weight :semi-bold
|
||||
:style {:color color}}
|
||||
(utils.string/get-initials full-name amount-initials)]))
|
||||
|
||||
(defn- lock
|
||||
[locked? size]
|
||||
;; When `locked?` is nil, we must not display the unlocked icon.
|
||||
(when (boolean? locked?)
|
||||
[rn/view
|
||||
{:accessibility-label :lock
|
||||
:style (style/lock-container size)}
|
||||
[icons/icon (if locked? :i/locked :i/unlocked)
|
||||
{:color (colors/theme-colors colors/neutral-50 colors/neutral-40)
|
||||
:container-style style/lock-icon
|
||||
:size 12}]]))
|
||||
|
||||
(defn view
|
||||
"Options:
|
||||
|
||||
:size - keyword (default nil) - Container size, for the moment,
|
||||
only :size/l (meaning large) is supported.
|
||||
|
||||
:emoji - string (default nil)
|
||||
|
||||
:customization-color - color (default nil) If the component is used for a
|
||||
community channel, then the default color should be the community custom
|
||||
color.
|
||||
|
||||
:locked? - nil/bool (default nil) - When true/false display a locked/unlocked
|
||||
icon respectively. When nil does not show icon.
|
||||
|
||||
:full-name - string (default nil) - When :emoji is blank, this value will be
|
||||
used to extract the initials.
|
||||
"
|
||||
[{:keys [size emoji customization-color locked? full-name]}]
|
||||
[rn/view
|
||||
{:accessibility-label :channel-avatar
|
||||
:style (style/outer-container {:size size :color customization-color})}
|
||||
(if (string/blank? emoji)
|
||||
[initials full-name size customization-color]
|
||||
[text/text
|
||||
{:accessibility-label :emoji
|
||||
:size (if (= size :size/l) :paragraph-1 :label)}
|
||||
(string/trim emoji)])
|
||||
[lock locked? size]])
|
|
@ -1,22 +1,9 @@
|
|||
(ns quo2.components.avatars.user-avatar.view
|
||||
(:require [clojure.string :as string]
|
||||
[quo2.components.avatars.user-avatar.style :as style]
|
||||
(:require [quo2.components.avatars.user-avatar.style :as style]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[react-native.core :as rn]
|
||||
[react-native.fast-image :as fast-image]))
|
||||
|
||||
(defn trim-whitespace [s] (string/join " " (string/split (string/trim s) #"\s+")))
|
||||
|
||||
(defn- extract-initials
|
||||
[full-name amount-initials]
|
||||
(let [upper-case-first-letter (comp string/upper-case first)
|
||||
names-list (string/split (trim-whitespace full-name) " ")]
|
||||
(if (= (first names-list) "")
|
||||
""
|
||||
(->> names-list
|
||||
(map upper-case-first-letter)
|
||||
(take amount-initials)
|
||||
(string/join)))))
|
||||
[react-native.fast-image :as fast-image]
|
||||
utils.string))
|
||||
|
||||
(defn initials-avatar
|
||||
[{:keys [full-name size draw-ring? customization-color]}]
|
||||
|
@ -29,7 +16,7 @@
|
|||
{:style style/initials-avatar-text
|
||||
:size font-size
|
||||
:weight :semi-bold}
|
||||
(extract-initials full-name amount-initials)]]))
|
||||
(utils.string/get-initials full-name amount-initials)]]))
|
||||
|
||||
(def valid-ring-sizes #{:big :medium :small})
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(ns quo2.components.list-items.channel
|
||||
(:require [quo2.components.avatars.channel-avatar :as channel-avatar]
|
||||
[quo2.components.common.unread-grey-dot.view :refer [unread-grey-dot]]
|
||||
(:require [quo2.components.avatars.channel-avatar.view :as channel-avatar]
|
||||
[quo2.components.common.unread-grey-dot.view :as unread-grey-dot]
|
||||
[quo2.components.counter.counter :as quo2.counter]
|
||||
[quo2.components.icon :as quo2.icons]
|
||||
[quo2.components.markdown.text :as quo2.text]
|
||||
|
@ -13,10 +13,11 @@
|
|||
|
||||
(defn list-item
|
||||
[{:keys [locked? mentions-count unread-messages?
|
||||
muted? is-active-channel? emoji channel-color]
|
||||
:or {channel-color colors/primary-50}
|
||||
muted? is-active-channel? emoji channel-color
|
||||
default-color]
|
||||
:as props}]
|
||||
(let [standard-props (apply dissoc props custom-props)
|
||||
(let [channel-color (or channel-color default-color)
|
||||
standard-props (apply dissoc props custom-props)
|
||||
name-text (:name props)]
|
||||
[rn/touchable-opacity standard-props
|
||||
[rn/view
|
||||
|
@ -36,11 +37,12 @@
|
|||
:align-items :center}
|
||||
:accessible true
|
||||
:accessibility-label :chat-name-text}
|
||||
[channel-avatar/channel-avatar
|
||||
{:big? true
|
||||
:locked? locked?
|
||||
:emoji-background-color (colors/theme-alpha channel-color 0.1 0.1)
|
||||
:emoji emoji}]
|
||||
[channel-avatar/view
|
||||
{:size :size/l
|
||||
:locked? locked?
|
||||
:full-name (:name props)
|
||||
:customization-color channel-color
|
||||
:emoji emoji}]
|
||||
[quo2.text/text
|
||||
{:style (cond-> {:margin-left 12}
|
||||
(and (not locked?) muted?)
|
||||
|
@ -63,4 +65,4 @@
|
|||
mentions-count]]
|
||||
|
||||
unread-messages?
|
||||
[unread-grey-dot :unviewed-messages-public])])]]))
|
||||
[unread-grey-dot/unread-grey-dot :unviewed-messages-public])])]]))
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
(:refer-clojure :exclude [filter])
|
||||
(:require
|
||||
quo2.components.avatars.account-avatar
|
||||
quo2.components.avatars.channel-avatar
|
||||
quo2.components.avatars.channel-avatar.view
|
||||
quo2.components.avatars.group-avatar
|
||||
quo2.components.avatars.icon-avatar
|
||||
quo2.components.avatars.user-avatar.view
|
||||
|
@ -121,7 +121,7 @@
|
|||
|
||||
;;;; AVATAR
|
||||
(def account-avatar quo2.components.avatars.account-avatar/account-avatar)
|
||||
(def channel-avatar quo2.components.avatars.channel-avatar/channel-avatar)
|
||||
(def channel-avatar quo2.components.avatars.channel-avatar.view/view)
|
||||
(def group-avatar quo2.components.avatars.group-avatar/group-avatar)
|
||||
(def icon-avatar quo2.components.avatars.icon-avatar/icon-avatar)
|
||||
(def user-avatar quo2.components.avatars.user-avatar.view/user-avatar)
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
(oops/oget event "nativeEvent.layout.y"))
|
||||
|
||||
(defn- channel-chat-item
|
||||
[community-id {chat-id :id muted? :muted? :as chat}]
|
||||
[community-id community-color {chat-id :id muted? :muted? :as chat}]
|
||||
(let [sheet-content [actions/chat-actions
|
||||
(assoc chat :chat-type constants/community-chat-type)
|
||||
false]
|
||||
|
@ -58,12 +58,13 @@
|
|||
[rn/view {:key chat-id :style {:margin-top 4}}
|
||||
[quo/channel-list-item
|
||||
(assoc chat
|
||||
:default-color community-color
|
||||
:on-long-press #(rf/dispatch [:show-bottom-sheet channel-sheet-data])
|
||||
:muted? (or muted?
|
||||
(rf/sub [:chat/check-channel-muted? community-id chat-id])))]]))
|
||||
|
||||
(defn channel-list-component
|
||||
[{:keys [on-category-layout community-id on-first-channel-height-changed]}
|
||||
[{:keys [on-category-layout community-id community-color on-first-channel-height-changed]}
|
||||
channels-list]
|
||||
[rn/view
|
||||
{:on-layout #(on-first-channel-height-changed
|
||||
|
@ -91,7 +92,7 @@
|
|||
:chevron-position :left}])
|
||||
(when-not collapsed?
|
||||
(into [rn/view {:style {:padding-horizontal 8 :padding-bottom 8}}]
|
||||
(map #(channel-chat-item community-id %))
|
||||
(map #(channel-chat-item community-id community-color %))
|
||||
chats))]))])
|
||||
|
||||
(defn request-to-join-text
|
||||
|
@ -262,7 +263,7 @@
|
|||
description])
|
||||
|
||||
(defn community-content
|
||||
[{:keys [name description joined tags id]
|
||||
[{:keys [name description joined tags color id]
|
||||
:as community}
|
||||
pending?
|
||||
{:keys [on-category-layout on-first-channel-height-changed]}]
|
||||
|
@ -280,6 +281,7 @@
|
|||
[channel-list-component
|
||||
{:on-category-layout on-category-layout
|
||||
:community-id id
|
||||
:community-color color
|
||||
:on-first-channel-height-changed on-first-channel-height-changed}
|
||||
(add-handlers-to-categorized-chats id chats-by-category)]]))
|
||||
|
||||
|
|
|
@ -1,53 +1,65 @@
|
|||
(ns status-im2.contexts.quo-preview.avatars.channel-avatar
|
||||
(:require [quo2.components.avatars.channel-avatar :as quo2]
|
||||
(:require [quo2.core :as quo]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[status-im2.contexts.quo-preview.preview :as preview]))
|
||||
|
||||
(def descriptor
|
||||
[{:label "Big?"
|
||||
:key :big?
|
||||
:type :boolean}
|
||||
{:label "Avatar color"
|
||||
:key :emoji-background-color
|
||||
:type :text}
|
||||
{:label "Avatar color"
|
||||
[{:label "Size:"
|
||||
:key :size
|
||||
:type :select
|
||||
:options [{:key :size/l :value "Large"}
|
||||
{:key :default :value "Default"}]}
|
||||
{:label "Emoji:"
|
||||
:key :emoji
|
||||
:type :text}
|
||||
{:label "is Locked?"
|
||||
:key :locked?
|
||||
{:label "Full name:"
|
||||
:key :full-name
|
||||
:type :text}
|
||||
(preview/customization-color-option)
|
||||
{:label "Locked state:"
|
||||
:key :locked-state
|
||||
:type :select
|
||||
:options [{:key nil
|
||||
:value "None"}
|
||||
{:key false
|
||||
:options [{:key :not-set
|
||||
:value "Not set"}
|
||||
{:key :unlocked
|
||||
:value "Unlocked"}
|
||||
{:key true
|
||||
{:key :locked
|
||||
:value "Locked"}]}])
|
||||
|
||||
(defn cool-preview
|
||||
[]
|
||||
(let [state (reagent/atom {:big? true
|
||||
:locked? nil
|
||||
:emoji "🍑"
|
||||
:emoji-background-color :gray})]
|
||||
(let [state (reagent/atom {:size :size/l
|
||||
:locked-state :not-set
|
||||
:emoji "🍑"
|
||||
:full-name "Some channel"
|
||||
:customization-color :blue})]
|
||||
(fn []
|
||||
[rn/touchable-without-feedback {:on-press rn/dismiss-keyboard!}
|
||||
[rn/view {:padding-bottom 150}
|
||||
[rn/view {:flex 1}
|
||||
[preview/customizer state descriptor]]
|
||||
[rn/view
|
||||
{:padding-vertical 60
|
||||
:flex-direction :row
|
||||
:justify-content :center}
|
||||
[quo2/channel-avatar @state]]]])))
|
||||
(let [customization-color (colors/custom-color-by-theme (:customization-color @state) 50 60)
|
||||
locked? (case (:locked-state @state)
|
||||
:not-set nil
|
||||
:unlocked false
|
||||
:locked true
|
||||
nil)]
|
||||
[rn/touchable-without-feedback {:on-press rn/dismiss-keyboard!}
|
||||
[rn/view {:style {:padding-bottom 150}}
|
||||
[rn/view {:style {:flex 1}}
|
||||
[preview/customizer state descriptor]]
|
||||
[rn/view
|
||||
{:style {:padding-vertical 60
|
||||
:flex-direction :row
|
||||
:justify-content :center}}
|
||||
[quo/channel-avatar
|
||||
(assoc @state
|
||||
:locked? locked?
|
||||
:customization-color customization-color)]]]]))))
|
||||
|
||||
(defn preview-channel-avatar
|
||||
[]
|
||||
[rn/view
|
||||
{:background-color (colors/theme-colors colors/white
|
||||
colors/neutral-90)
|
||||
:flex 1}
|
||||
{:style {:background-color (colors/theme-colors colors/white colors/neutral-95)
|
||||
:flex 1}}
|
||||
[rn/flat-list
|
||||
{:flex 1
|
||||
:keyboard-should-persist-taps :always
|
||||
|
|
|
@ -202,6 +202,20 @@
|
|||
:select [customizer-select descriptor]
|
||||
nil)]))])
|
||||
|
||||
(defn customization-color-option
|
||||
([]
|
||||
(customization-color-option {}))
|
||||
([opts]
|
||||
(merge {:label "Custom color:"
|
||||
:key :customization-color
|
||||
:type :select
|
||||
:options (->> colors/customization
|
||||
keys
|
||||
sort
|
||||
(map (fn [k]
|
||||
{:key k :value (string/capitalize (name k))})))}
|
||||
opts)))
|
||||
|
||||
(comment
|
||||
[{:label "Show error:"
|
||||
:key :error
|
||||
|
|
|
@ -41,8 +41,8 @@
|
|||
{:style {:flex-direction :row
|
||||
:align-items :center}}
|
||||
[quo/channel-avatar
|
||||
{:emoji (:emoji community-channel)
|
||||
:emoji-background-color (colors/alpha color-50 0.1)}]
|
||||
{:emoji (:emoji community-channel)
|
||||
:customization-color color-50}]
|
||||
[quo/text
|
||||
{:size :paragraph-2
|
||||
:weight :medium
|
||||
|
|
|
@ -49,3 +49,12 @@
|
|||
[s m r]
|
||||
(when (string? s)
|
||||
(string/replace s m r)))
|
||||
|
||||
(defn get-initials
|
||||
"Returns `n` number of initial letters from `s`, all uppercased."
|
||||
[s n]
|
||||
(let [words (-> s str string/trim (string/split #"\s+"))]
|
||||
(->> words
|
||||
(take n)
|
||||
(map (comp string/upper-case str first))
|
||||
string/join)))
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
(ns utils.string-test
|
||||
(:require [cljs.test :refer [are deftest]]
|
||||
utils.string))
|
||||
|
||||
(deftest get-initials-test
|
||||
(are [expected input amount-initials]
|
||||
(= expected (utils.string/get-initials input amount-initials))
|
||||
"" nil 0
|
||||
"" nil 1
|
||||
"" "" 0
|
||||
"" "ab" 0
|
||||
"" "" 1
|
||||
"A" "ab" 1
|
||||
"A" " ab " 1
|
||||
"A" "a b" 1
|
||||
"A" "ab" 2
|
||||
"AB" "a b" 2
|
||||
"ABC" "a b c d" 3
|
||||
"ABC" " a b c d" 3))
|
Loading…
Reference in New Issue