Skip to content

Commit bee5e5e

Browse files
Initial commit
1 parent a21a897 commit bee5e5e

10 files changed

Lines changed: 220 additions & 90 deletions

File tree

src/components/Dialog/hooks/useDialog.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,17 @@ export const useDialog = ({
2222
}: UseDialogParams) => {
2323
const { dialogManager } = useDialogManager({ dialogManagerId });
2424

25-
useEffect(
26-
() => () => {
25+
useEffect(() => {
26+
dialogManager.cancelPendingRemoval(id);
27+
28+
return () => {
2729
// Since this cleanup can run even if the component is still mounted
2830
// and dialog id is unchanged (e.g. in <StrictMode />), it's safer to
2931
// mark state as unused and only remove it after a timeout, rather than
3032
// to remove it immediately.
3133
dialogManager.markForRemoval(id);
32-
},
33-
[dialogManager, id],
34-
);
34+
};
35+
}, [dialogManager, id]);
3536

3637
return dialogManager.getOrCreate({ closeOnClickOutside, id });
3738
};

src/components/Dialog/service/DialogManager.ts

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,12 @@ export class DialogManager {
7070
);
7171
}
7272

73-
get(id: DialogId) {
73+
get(id: DialogId): Dialog | undefined {
7474
return this.state.getLatestValue().dialogsById[id];
7575
}
7676

7777
getOrCreate({ closeOnClickOutside, id }: GetOrCreateDialogParams) {
78-
let dialog = this.state.getLatestValue().dialogsById[id];
78+
let dialog = this.get(id);
7979
if (!dialog) {
8080
dialog = {
8181
close: () => {
@@ -97,7 +97,7 @@ export class DialogManager {
9797
};
9898
this.state.next((current) => ({
9999
...current,
100-
dialogsById: { ...current.dialogsById, [id]: dialog },
100+
dialogsById: { ...current.dialogsById, [id]: dialog as Dialog },
101101
}));
102102
}
103103

@@ -106,16 +106,15 @@ export class DialogManager {
106106

107107
if (shouldUpdateDialogSettings) {
108108
if (dialog.removalTimeout) clearTimeout(dialog.removalTimeout);
109-
dialog = {
110-
...dialog,
111-
closeOnClickOutside,
112-
removalTimeout: undefined,
113-
};
114109
this.state.next((current) => ({
115110
...current,
116111
dialogsById: {
117112
...current.dialogsById,
118-
[id]: dialog,
113+
[id]: {
114+
...current.dialogsById[id],
115+
closeOnClickOutside,
116+
removalTimeout: undefined,
117+
},
119118
},
120119
}));
121120
}
@@ -158,9 +157,8 @@ export class DialogManager {
158157
}
159158
}
160159

161-
remove(id: DialogId) {
162-
const state = this.state.getLatestValue();
163-
const dialog = state.dialogsById[id];
160+
remove = (id: DialogId) => {
161+
const dialog = this.get(id);
164162
if (!dialog) return;
165163

166164
if (dialog.removalTimeout) {
@@ -175,15 +173,15 @@ export class DialogManager {
175173
dialogsById: newDialogs,
176174
};
177175
});
178-
}
176+
};
179177

180178
/**
181179
* Marks the dialog state as unused. If the dialog id is referenced again quickly,
182180
* the state will not be removed. Otherwise, the state will be removed after
183181
* a short timeout.
184182
*/
185183
markForRemoval(id: DialogId) {
186-
const dialog = this.state.getLatestValue().dialogsById[id];
184+
const dialog = this.get(id);
187185

188186
if (!dialog) {
189187
return;
@@ -202,4 +200,25 @@ export class DialogManager {
202200
},
203201
}));
204202
}
203+
204+
cancelPendingRemoval(id: DialogId) {
205+
const dialog = this.get(id);
206+
207+
if (!dialog?.removalTimeout) {
208+
return;
209+
}
210+
211+
clearTimeout(dialog.removalTimeout);
212+
213+
this.state.next((current) => ({
214+
...current,
215+
dialogsById: {
216+
...current.dialogsById,
217+
[id]: {
218+
...current.dialogsById[id],
219+
removalTimeout: undefined,
220+
},
221+
},
222+
}));
223+
}
205224
}

src/components/Icons/icons.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,32 @@ export const IconEmoji = createIcon(
495495
</>,
496496
);
497497

498+
export const IconEmojiAdd = createIcon(
499+
'IconEmojiAdd',
500+
<>
501+
<path
502+
d='M1.75 10C1.75 5.44365 5.44365 1.75 10 1.75C10.4142 1.75 10.75 2.08579 10.75 2.5C10.75 2.91421 10.4142 3.25 10 3.25C6.27208 3.25 3.25 6.27208 3.25 10C3.25 13.7279 6.27208 16.75 10 16.75C13.7279 16.75 16.75 13.7279 16.75 10C16.75 9.58579 17.0858 9.25 17.5 9.25C17.9142 9.25 18.25 9.58579 18.25 10C18.25 14.5563 14.5563 18.25 10 18.25C5.44365 18.25 1.75 14.5563 1.75 10Z'
503+
fill='currentColor'
504+
/>
505+
<path
506+
d='M7.1875 9.375C7.70527 9.375 8.125 8.95527 8.125 8.4375C8.125 7.91973 7.70527 7.5 7.1875 7.5C6.66973 7.5 6.25 7.91973 6.25 8.4375C6.25 8.95527 6.66973 9.375 7.1875 9.375Z'
507+
fill='currentColor'
508+
/>
509+
<path
510+
d='M12.8125 9.375C13.3303 9.375 13.75 8.95527 13.75 8.4375C13.75 7.91973 13.3303 7.5 12.8125 7.5C12.2947 7.5 11.875 7.91973 11.875 8.4375C11.875 8.95527 12.2947 9.375 12.8125 9.375Z'
511+
fill='currentColor'
512+
/>
513+
<path
514+
d='M12.4756 11.499C12.683 11.1407 13.1425 11.0182 13.501 11.2256C13.8593 11.433 13.9818 11.8925 13.7744 12.251C13.0125 13.568 11.6947 14.5 10 14.5C8.30531 14.5 6.98748 13.568 6.22559 12.251C6.01825 11.8925 6.14067 11.433 6.49902 11.2256C6.85749 11.0182 7.31695 11.1407 7.52441 11.499C8.05942 12.424 8.91824 13 10 13C11.0818 13 11.9406 12.424 12.4756 11.499Z'
515+
fill='currentColor'
516+
/>
517+
<path
518+
d='M15.083 6.87524V4.91626H13.125C12.7108 4.91626 12.375 4.58047 12.375 4.16626C12.3752 3.7522 12.7109 3.41626 13.125 3.41626H15.083V1.45825C15.083 1.04415 15.4189 0.708427 15.833 0.708252C16.2472 0.708252 16.583 1.04404 16.583 1.45825V3.41626H18.542C18.9559 3.41644 19.2918 3.7523 19.292 4.16626C19.292 4.58036 18.9561 4.91608 18.542 4.91626H16.583V6.87524C16.5828 7.28931 16.2471 7.62524 15.833 7.62524C15.4191 7.62507 15.0832 7.2892 15.083 6.87524Z'
519+
fill='currentColor'
520+
/>
521+
</>,
522+
);
523+
498524
// was: IconExclamation
499525
export const IconExclamationMarkFill = createIcon(
500526
'IconExclamationMarkFill',

src/components/Message/styling/Message.scss

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -278,9 +278,7 @@
278278
var(--str-chat__message-reactions-host-offset-x) * -1
279279
);
280280

281-
&:has(.str-chat__message-reactions--flipped-horizontally) {
282-
margin-inline-end: var(--str-chat__message-reactions-host-offset-x);
283-
}
281+
margin-inline-end: var(--str-chat__message-reactions-host-offset-x);
284282
}
285283

286284
.str-chat__message-reactions.str-chat__message-reactions--segmented.str-chat__message-reactions--bottom
@@ -323,9 +321,7 @@
323321
&:has(.str-chat__message-reactions--top) {
324322
padding-inline-end: calc(var(--str-chat__message-reactions-host-offset-x) * -1);
325323

326-
&:has(.str-chat__message-reactions--flipped-horizontally) {
327-
margin-inline-start: var(--str-chat__message-reactions-host-offset-x);
328-
}
324+
margin-inline-start: var(--str-chat__message-reactions-host-offset-x);
329325
}
330326

331327
.str-chat__message-reactions.str-chat__message-reactions--segmented.str-chat__message-reactions--bottom

src/components/Reactions/MessageReactionsDetail.tsx

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useMemo } from 'react';
1+
import React, { useMemo, useState } from 'react';
22

33
import type { ReactionSummary, ReactionType } from './types';
44

@@ -14,6 +14,8 @@ import {
1414
import type { ReactionSort } from 'stream-chat';
1515
import { defaultReactionOptions } from './reactionOptions';
1616
import type { useProcessReactions } from './hooks/useProcessReactions';
17+
import { IconEmojiAdd } from '../Icons';
18+
import { ReactionSelector, type ReactionSelectorProps } from './ReactionSelector';
1719

1820
export type MessageReactionsDetailProps = Partial<
1921
Pick<MessageContextValue, 'handleFetchReactions' | 'reactionDetailsSort'>
@@ -24,7 +26,7 @@ export type MessageReactionsDetailProps = Partial<
2426
sort?: ReactionSort;
2527
totalReactionCount?: number;
2628
reactionGroups?: ReturnType<typeof useProcessReactions>['reactionGroups'];
27-
};
29+
} & ReactionSelectorProps;
2830

2931
const defaultReactionDetailsSort = { created_at: -1 } as const;
3032

@@ -45,13 +47,16 @@ export const MessageReactionsDetailLoadingIndicator = () => {
4547

4648
export function MessageReactionsDetail({
4749
handleFetchReactions,
50+
handleReaction,
4851
onSelectedReactionTypeChange,
52+
own_reactions,
4953
reactionDetailsSort: propReactionDetailsSort,
5054
reactionGroups,
5155
reactions,
5256
selectedReactionType,
5357
totalReactionCount,
5458
}: MessageReactionsDetailProps) {
59+
const [extendedReactionListOpen, setExtendedReactionListOpen] = useState(false);
5560
const { client } = useChatContext();
5661
const {
5762
Avatar = DefaultAvatar,
@@ -62,6 +67,7 @@ export function MessageReactionsDetail({
6267

6368
const {
6469
handleReaction: contextHandleReaction,
70+
message,
6571
reactionDetailsSort: contextReactionDetailsSort,
6672
} = useMessageContext(MessageReactionsDetail.name);
6773

@@ -79,6 +85,21 @@ export function MessageReactionsDetail({
7985
sort: reactionDetailsSort,
8086
});
8187

88+
if (extendedReactionListOpen) {
89+
return (
90+
<div
91+
className='str-chat__message-reactions-detail'
92+
data-testid='message-reactions-detail'
93+
>
94+
<ReactionSelector.ExtendedList
95+
dialogId={`message-reactions-detail-${message.id}`}
96+
handleReaction={handleReaction}
97+
own_reactions={own_reactions}
98+
/>
99+
</div>
100+
);
101+
}
102+
82103
return (
83104
<div
84105
className='str-chat__message-reactions-detail'
@@ -91,6 +112,17 @@ export function MessageReactionsDetail({
91112
)}
92113
<div className='str-chat__message-reactions-detail__reaction-type-list-container'>
93114
<ul className='str-chat__message-reactions-detail__reaction-type-list'>
115+
<li className='str-chat__message-reactions-detail__reaction-type-list-item'>
116+
<button
117+
className='str-chat__message-reactions-detail__reaction-type-list-item-button'
118+
onClick={() => setExtendedReactionListOpen(true)}
119+
>
120+
<span className='str-chat__message-reactions-detail__reaction-type-list-item-icon'>
121+
<IconEmojiAdd />
122+
</span>
123+
</button>
124+
</li>
125+
94126
{reactions.map(
95127
({ EmojiComponent, reactionCount, reactionType }) =>
96128
EmojiComponent && (
@@ -110,14 +142,12 @@ export function MessageReactionsDetail({
110142
<span className='str-chat__message-reactions-detail__reaction-type-list-item-icon'>
111143
<EmojiComponent />
112144
</span>
113-
{reactionCount > 1 && (
114-
<span
115-
className='str-chat__message-reactions-detail__reaction-type-list-item-count'
116-
data-testclass='message-reactions-item-count'
117-
>
118-
{reactionCount}
119-
</span>
120-
)}
145+
<span
146+
className='str-chat__message-reactions-detail__reaction-type-list-item-count'
147+
data-testclass='message-reactions-item-count'
148+
>
149+
{reactionCount}
150+
</span>
121151
</button>
122152
</li>
123153
),

0 commit comments

Comments
 (0)