Skip to content

Commit 59fadfb

Browse files
ReactionsListModal stuff
1 parent 2412531 commit 59fadfb

15 files changed

Lines changed: 279 additions & 57 deletions

File tree

src/components/Reactions/ReactionsListModal.tsx

Lines changed: 87 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ import { Modal as DefaultModal } from '../Modal';
77
import { useFetchReactions } from './hooks/useFetchReactions';
88
import { LoadingIndicator } from '../Loading';
99
import { Avatar } from '../Avatar';
10-
import { useComponentContext, useMessageContext } from '../../context';
10+
import {
11+
useChatContext,
12+
useComponentContext,
13+
useMessageContext,
14+
useTranslationContext,
15+
} from '../../context';
1116
import type { ReactionSort } from 'stream-chat';
1217
import type { ModalProps } from '../Modal';
1318
import type { MessageContextValue } from '../../context';
@@ -34,11 +39,11 @@ export function ReactionsListModal({
3439
...modalProps
3540
}: ReactionsListModalProps) {
3641
const { Modal = DefaultModal } = useComponentContext();
37-
const selectedReaction = reactions.find(
38-
({ reactionType }) => reactionType === selectedReactionType,
39-
);
40-
const SelectedEmojiComponent = selectedReaction?.EmojiComponent ?? null;
42+
const { client } = useChatContext();
43+
const { t } = useTranslationContext();
44+
4145
const {
46+
message,
4247
reactionDetailsSort: contextReactionDetailsSort,
4348
sortReactionDetails: contextSortReactionDetails,
4449
} = useMessageContext('ReactionsListModal');
@@ -53,6 +58,15 @@ export function ReactionsListModal({
5358
sort: reactionDetailsSort,
5459
});
5560

61+
const totalReactionCount = useMemo(
62+
() =>
63+
Object.entries(message.reaction_counts ?? {}).reduce(
64+
(total, [, reactionCount]) => total + reactionCount,
65+
0,
66+
),
67+
[message.reaction_counts],
68+
);
69+
5670
const reactionDetailsWithLegacyFallback = useMemo(
5771
() =>
5872
legacySortReactionDetails
@@ -64,70 +78,86 @@ export function ReactionsListModal({
6478
return (
6579
<Modal
6680
{...modalProps}
67-
className={clsx('str-chat__message-reactions-details-modal', modalProps.className)}
81+
className={clsx('str-chat__message-reactions-detail-modal', modalProps.className)}
6882
>
6983
<div
70-
className='str-chat__message-reactions-details'
84+
className='str-chat__message-reactions-detail'
7185
data-testid='reactions-list-modal'
7286
>
73-
<div className='str-chat__message-reactions-details-reaction-types'>
74-
{reactions.map(
75-
({ EmojiComponent, reactionCount, reactionType }) =>
76-
EmojiComponent && (
77-
<div
78-
className={clsx('str-chat__message-reactions-details-reaction-type', {
79-
'str-chat__message-reactions-details-reaction-type--selected':
80-
selectedReactionType === reactionType,
81-
})}
82-
data-testid={`reaction-details-selector-${reactionType}`}
83-
key={reactionType}
84-
onClick={() =>
85-
onSelectedReactionTypeChange?.(reactionType as ReactionType)
86-
}
87-
>
88-
<span className='str-chat__message-reaction-emoji str-chat__message-reaction-emoji--with-fallback'>
89-
<EmojiComponent />
90-
</span>
91-
&nbsp;
92-
<span className='str-chat__message-reaction-count'>
93-
{reactionCount}
94-
</span>
95-
</div>
96-
),
97-
)}
87+
<div className='str-chat__message-reactions-detail__total-count'>
88+
{t('{{ count }} reactions', { count: totalReactionCount })}
89+
</div>
90+
<div className='str-chat__message-reactions-detail__reaction-type-list-container'>
91+
<ul className='str-chat__message-reactions-detail__reaction-type-list'>
92+
{reactions.map(
93+
({ EmojiComponent, reactionCount, reactionType }) =>
94+
EmojiComponent && (
95+
<li
96+
className='str-chat__message-reactions-detail__reaction-type-list-item'
97+
key={reactionType}
98+
>
99+
<button
100+
aria-pressed={reactionType === selectedReactionType}
101+
className='str-chat__message-reactions-detail__reaction-type-list-item-button'
102+
onClick={() => onSelectedReactionTypeChange?.(reactionType)}
103+
>
104+
<span className='str-chat__message-reactions-detail__reaction-type-list-item-icon'>
105+
<EmojiComponent />
106+
</span>
107+
{reactionCount > 1 && (
108+
<span
109+
className='str-chat__message-reactions-detail__reaction-type-list-item-count'
110+
data-testclass='message-reactions-item-count'
111+
>
112+
{reactionCount}
113+
</span>
114+
)}
115+
</button>
116+
</li>
117+
),
118+
)}
119+
</ul>
98120
</div>
99-
{SelectedEmojiComponent && (
100-
<div className='str-chat__message-reaction-emoji str-chat__message-reaction-emoji--with-fallback str-chat__message-reaction-emoji-big'>
101-
<SelectedEmojiComponent />
102-
</div>
103-
)}
104121
<div
105-
className='str-chat__message-reactions-details-reacting-users'
122+
className='str-chat__message-reactions-detail__user-list'
106123
data-testid='all-reacting-users'
107124
>
108125
{areReactionsLoading ? (
109126
<LoadingIndicator />
110127
) : (
111-
reactionDetailsWithLegacyFallback.map(({ user }) => (
112-
<div
113-
className='str-chat__message-reactions-details-reacting-user'
114-
key={user?.id}
115-
>
116-
<Avatar
117-
className='stream-chat__avatar--reaction'
118-
data-testid='avatar'
119-
imageUrl={user?.image as string | undefined}
120-
size='md'
121-
userName={user?.name || user?.id}
122-
/>
123-
<span
124-
className='str-chat__user-item--name'
125-
data-testid='reaction-user-username'
128+
reactionDetailsWithLegacyFallback.map(({ user }) => {
129+
const belongsToCurrentUser = client.user?.id === user?.id;
130+
return (
131+
<div
132+
className='str-chat__message-reactions-detail__user-list-item'
133+
key={user?.id}
126134
>
127-
{user?.name || user?.id}
128-
</span>
129-
</div>
130-
))
135+
<Avatar
136+
className='str-chat__avatar--with-border'
137+
data-testid='avatar'
138+
imageUrl={user?.image as string | undefined}
139+
size='sm'
140+
userName={user?.name || user?.id}
141+
/>
142+
<div className='str-chat__message-reactions-detail__user-list-item-info'>
143+
<span
144+
className='str-chat__message-reactions-detail__user-list-item-username'
145+
data-testid='reaction-user-username'
146+
>
147+
{belongsToCurrentUser ? t('You') : user?.name || user?.id}
148+
</span>
149+
{belongsToCurrentUser && (
150+
<button
151+
className='str-chat__message-reactions-detail__user-list-item-button'
152+
data-testid='remove-reaction-button'
153+
>
154+
{t('Tap to remove')}
155+
</button>
156+
)}
157+
</div>
158+
</div>
159+
);
160+
})
131161
)}
132162
</div>
133163
</div>
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
@use '../../../styling/utils' as utils;
2+
3+
.str-chat__message-reactions-detail-modal {
4+
}
5+
6+
.str-chat__message-reactions-detail {
7+
padding-block-start: var(--spacing-xxs);
8+
9+
min-width: min(90vw, 400px);
10+
max-width: 100%;
11+
12+
display: flex;
13+
flex-direction: column;
14+
gap: var(--spacing-xxs);
15+
16+
.str-chat__message-reactions-detail__total-count {
17+
display: flex;
18+
align-items: center;
19+
justify-content: flex-start;
20+
padding-inline: var(--spacing-md);
21+
padding-block: 6px;
22+
23+
color: var(--text-tertiary, #687385);
24+
25+
font-family: var(--typography-font-family-sans);
26+
font-size: var(--typography-font-size-sm);
27+
font-weight: var(--typography-font-weight-medium);
28+
line-height: var(--typography-line-height-normal);
29+
}
30+
31+
.str-chat__message-reactions-detail__reaction-type-list-container {
32+
display: flex;
33+
overflow-x: auto;
34+
width: 100%;
35+
36+
scrollbar-color: red orange;
37+
scrollbar-width: none;
38+
}
39+
40+
.str-chat__message-reactions-detail__reaction-type-list {
41+
list-style: none;
42+
margin: 0;
43+
padding-inline: var(--spacing-md);
44+
padding-block: var(--spacing-xs);
45+
display: flex;
46+
align-items: center;
47+
gap: var(--spacing-xxs);
48+
49+
display: flex;
50+
gap: var(--spacing-xxs);
51+
52+
.str-chat__message-reactions-detail__reaction-type-list-item {
53+
display: flex;
54+
55+
.str-chat__message-reactions-detail__reaction-type-list-item-button {
56+
@include utils.unset-button;
57+
user-select: none;
58+
display: flex;
59+
cursor: pointer;
60+
position: relative;
61+
display: flex;
62+
padding: var(--spacing-xxs) var(--spacing-xs);
63+
align-items: center;
64+
justify-content: center;
65+
border-radius: var(--radius-max);
66+
border: 1px solid var(--reaction-border);
67+
background: var(--reaction-bg);
68+
line-height: 1;
69+
gap: var(--spacing-xxs);
70+
71+
.str-chat__message-reactions-detail__reaction-type-list-item-icon {
72+
// FIXME: ridiculous hack so that the emoji block is in a square container (1/1 ratio)
73+
font-size: 13px;
74+
line-height: 16px;
75+
font-family: system-ui;
76+
font-style: normal;
77+
letter-spacing: 0;
78+
display: flex;
79+
}
80+
81+
.str-chat__message-reactions-detail__reaction-type-list-item-count {
82+
color: var(--reaction-text);
83+
font-family: var(--typography-font-family-sans);
84+
font-size: var(--typography-font-size-xxs);
85+
font-style: normal;
86+
font-weight: var(--typography-font-weight-bold);
87+
}
88+
89+
&:not(:disabled) {
90+
&:hover,
91+
&:active,
92+
&[aria-pressed='true'] {
93+
&::before {
94+
content: '';
95+
position: absolute;
96+
inset: 0;
97+
border-radius: inherit;
98+
width: 100%;
99+
height: 100%;
100+
}
101+
}
102+
103+
&:hover::before {
104+
background: var(--background-core-hover);
105+
}
106+
&:active::before {
107+
background: var(--background-core-pressed);
108+
}
109+
&[aria-pressed='true']::before {
110+
background: var(--background-core-selected);
111+
}
112+
}
113+
}
114+
}
115+
}
116+
117+
.str-chat__message-reactions-detail__user-list {
118+
.str-chat__message-reactions-detail__user-list-item {
119+
padding-block: var(--spacing-xs);
120+
display: flex;
121+
align-items: center;
122+
gap: var(--spacing-xs);
123+
padding-inline: var(--spacing-md);
124+
125+
.str-chat__message-reactions-detail__user-list-item-info {
126+
display: flex;
127+
flex-direction: column;
128+
gap: var(--spacing-xxxs);
129+
130+
.str-chat__message-reactions-detail__user-list-item-username {
131+
color: var(--text-primary);
132+
font-family: var(--typography-font-family-sans);
133+
font-size: var(--typography-font-size-sm);
134+
font-weight: var(--typography-font-weight-regular);
135+
line-height: var(--typography-line-height-normal);
136+
}
137+
138+
.str-chat__message-reactions-detail__user-list-item-button {
139+
@include utils.unset-button;
140+
color: var(--text-tertiary);
141+
font-family: var(--typography-font-family-sans);
142+
font-size: var(--typography-font-size-xs);
143+
font-style: normal;
144+
font-weight: var(--typography-font-weight-regular);
145+
line-height: var(--typography-line-height-tight);
146+
cursor: pointer;
147+
}
148+
}
149+
}
150+
}
151+
}

src/i18n/de.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
"{{ count }} files_other": "{{ count }} Dateien",
66
"{{ count }} photos_one": "{{ count }} Foto",
77
"{{ count }} photos_other": "{{ count }} Fotos",
8+
"{{ count }} reactions_one": "{{ count }} reactions",
9+
"{{ count }} reactions_other": "{{ count }} reactions",
810
"{{ count }} videos_one": "{{ count }} Video",
911
"{{ count }} videos_other": "{{ count }} Videos",
1012
"{{ firstUser }} and {{ secondUser }}": "{{ firstUser }} und {{ secondUser }}",
@@ -267,6 +269,7 @@
267269
"Stop sharing": "Teilen beenden",
268270
"Submit": "Absenden",
269271
"Suggest an option": "Eine Option vorschlagen",
272+
"Tap to remove": "Tap to remove",
270273
"Thinking...": "Denken...",
271274
"this content could not be displayed": "Dieser Inhalt konnte nicht angezeigt werden",
272275
"This field cannot be empty or contain only spaces": "Dieses Feld darf nicht leer sein oder nur Leerzeichen enthalten",

src/i18n/en.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
"{{ count }} files_other": "{{ count }} files",
66
"{{ count }} photos_one": "{{ count }} photo",
77
"{{ count }} photos_other": "{{ count }} photos",
8+
"{{ count }} reactions_one": "{{ count }} reaction",
9+
"{{ count }} reactions_other": "{{ count }} reactions",
810
"{{ count }} videos_one": "{{ count }} video",
911
"{{ count }} videos_other": "{{ count }} videos",
1012
"{{ firstUser }} and {{ secondUser }}": "{{ firstUser }} and {{ secondUser }}",
@@ -267,6 +269,7 @@
267269
"Stop sharing": "Stop sharing",
268270
"Submit": "Submit",
269271
"Suggest an option": "Suggest an option",
272+
"Tap to remove": "Tap to remove",
270273
"Thinking...": "Thinking...",
271274
"this content could not be displayed": "this content could not be displayed",
272275
"This field cannot be empty or contain only spaces": "This field cannot be empty or contain only spaces",

src/i18n/es.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
"{{ count }} photos_one": "{{ count }} foto",
88
"{{ count }} photos_many": "{{ count }} fotos",
99
"{{ count }} photos_other": "{{ count }} fotos",
10+
"{{ count }} reactions_one": "",
11+
"{{ count }} reactions_many": "",
12+
"{{ count }} reactions_other": "",
1013
"{{ count }} videos_one": "{{ count }} vídeo",
1114
"{{ count }} videos_many": "{{ count }} vídeos",
1215
"{{ count }} videos_other": "{{ count }} vídeos",
@@ -276,6 +279,7 @@
276279
"Stop sharing": "Dejar de compartir",
277280
"Submit": "Enviar",
278281
"Suggest an option": "Sugerir una opción",
282+
"Tap to remove": "",
279283
"Thinking...": "Pensando...",
280284
"this content could not be displayed": "Este contenido no se pudo mostrar",
281285
"This field cannot be empty or contain only spaces": "Este campo no puede estar vacío o contener solo espacios",

0 commit comments

Comments
 (0)