Skip to content

Commit f9dfe26

Browse files
committed
✨(frontend) add an EmojiPicker in the document tree
As discussed here #1358 (comment)
1 parent 116baff commit f9dfe26

File tree

8 files changed

+433
-62
lines changed

8 files changed

+433
-62
lines changed

src/frontend/apps/impress/src/features/docs/doc-editor/components/EmojiPicker.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export const EmojiPicker = ({
1919
const { i18n } = useTranslation();
2020

2121
return (
22-
<Box $position="absolute" $zIndex={1000} $margin="2rem 0 0 0">
22+
<Box>
2323
<Picker
2424
data={emojiData}
2525
locale={i18n.resolvedLanguage}

src/frontend/apps/impress/src/features/docs/doc-header/components/DocTitle.tsx

Lines changed: 7 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,13 @@
1-
import { useTreeContext } from '@gouvfr-lasuite/ui-kit';
21
import { Tooltip } from '@openfun/cunningham-react';
32
import React, { useCallback, useEffect, useState } from 'react';
43
import { useTranslation } from 'react-i18next';
54
import { css } from 'styled-components';
65

76
import { Box, Text } from '@/components';
87
import { useCunninghamTheme } from '@/cunningham';
9-
import {
10-
Doc,
11-
KEY_DOC,
12-
KEY_LIST_DOC,
13-
useDocStore,
14-
useTrans,
15-
useUpdateDoc,
16-
} from '@/docs/doc-management';
17-
import { useBroadcastStore, useResponsiveStore } from '@/stores';
8+
import { Doc, useDocStore, useTrans } from '@/docs/doc-management';
9+
import { useDocTitleUpdate } from '@/features/docs/doc-management/hooks/useDocTitleUpdate';
10+
import { useResponsiveStore } from '@/stores';
1811

1912
interface DocTitleProps {
2013
doc: Doc;
@@ -50,47 +43,17 @@ const DocTitleInput = ({ doc }: DocTitleProps) => {
5043
const { t } = useTranslation();
5144
const { colorsTokens } = useCunninghamTheme();
5245
const [titleDisplay, setTitleDisplay] = useState(doc.title);
53-
const treeContext = useTreeContext<Doc>();
5446

5547
const { untitledDocument } = useTrans();
5648

57-
const { broadcast } = useBroadcastStore();
58-
59-
const { mutate: updateDoc } = useUpdateDoc({
60-
listInvalideQueries: [KEY_DOC, KEY_LIST_DOC],
61-
onSuccess(updatedDoc) {
62-
// Broadcast to every user connected to the document
63-
broadcast(`${KEY_DOC}-${updatedDoc.id}`);
64-
65-
if (!treeContext) {
66-
return;
67-
}
68-
69-
if (treeContext.root?.id === updatedDoc.id) {
70-
treeContext?.setRoot(updatedDoc);
71-
} else {
72-
treeContext?.treeData.updateNode(updatedDoc.id, updatedDoc);
73-
}
74-
},
75-
});
49+
const { updateDocTitle } = useDocTitleUpdate();
7650

7751
const handleTitleSubmit = useCallback(
7852
(inputText: string) => {
79-
let sanitizedTitle = inputText.trim();
80-
sanitizedTitle = sanitizedTitle.replace(/(\r\n|\n|\r)/gm, '');
81-
82-
// When blank we set to untitled
83-
if (!sanitizedTitle) {
84-
setTitleDisplay('');
85-
}
86-
87-
// If mutation we update
88-
if (sanitizedTitle !== doc.title) {
89-
setTitleDisplay(sanitizedTitle);
90-
updateDoc({ id: doc.id, title: sanitizedTitle });
91-
}
53+
const sanitizedTitle = updateDocTitle(doc, inputText.trim());
54+
setTitleDisplay(sanitizedTitle);
9255
},
93-
[doc.id, doc.title, updateDoc],
56+
[doc, updateDocTitle],
9457
);
9558

9659
const handleKeyDown = (e: React.KeyboardEvent) => {
Lines changed: 93 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
1+
import React from 'react';
2+
import { createPortal } from 'react-dom';
13
import { useTranslation } from 'react-i18next';
24

3-
import { Text, TextType } from '@/components';
5+
import { Box, Icon, TextType } from '@/components';
6+
7+
import { EmojiPicker } from '../../doc-editor/components/EmojiPicker';
8+
import emojidata from '../../doc-editor/components/custom-blocks/initEmojiCallout';
9+
import { useDocTitleUpdate } from '../hooks/useDocTitleUpdate';
410

511
type DocIconProps = TextType & {
612
emoji?: string | null;
13+
emojiPicker?: boolean;
714
defaultIcon: React.ReactNode;
15+
docId?: string;
16+
title?: string;
17+
onEmojiUpdate?: (emoji: string) => void;
818
};
919

1020
export const DocIcon = ({
@@ -13,24 +23,93 @@ export const DocIcon = ({
1323
$size = 'sm',
1424
$variation = '1000',
1525
$weight = '400',
26+
emojiPicker = false,
27+
docId,
28+
title,
29+
onEmojiUpdate,
1630
...textProps
1731
}: DocIconProps) => {
1832
const { t } = useTranslation();
33+
const { updateDocEmoji } = useDocTitleUpdate();
34+
35+
const iconRef = React.useRef<HTMLDivElement>(null);
36+
37+
const [openEmojiPicker, setOpenEmojiPicker] = React.useState<boolean>(false);
38+
const [pickerPosition, setPickerPosition] = React.useState<{
39+
top: number;
40+
left: number;
41+
}>({ top: 0, left: 0 });
42+
43+
const toggleEmojiPicker = (e: React.MouseEvent) => {
44+
if (emojiPicker) {
45+
e.stopPropagation();
46+
e.preventDefault();
47+
48+
if (!openEmojiPicker && iconRef.current) {
49+
const rect = iconRef.current.getBoundingClientRect();
50+
setPickerPosition({
51+
top: rect.bottom + window.scrollY + 8,
52+
left: rect.left + window.scrollX,
53+
});
54+
}
55+
56+
setOpenEmojiPicker(!openEmojiPicker);
57+
}
58+
};
59+
60+
const handleEmojiSelect = ({ native }: { native: string }) => {
61+
setOpenEmojiPicker(false);
62+
63+
// Update document emoji if docId is provided
64+
if (docId && title !== undefined) {
65+
updateDocEmoji(docId, title ?? '', native);
66+
}
67+
68+
// Call the optional callback
69+
onEmojiUpdate?.(native);
70+
};
1971

20-
if (!emoji) {
21-
return <>{defaultIcon}</>;
22-
}
72+
const handleClickOutside = () => {
73+
setOpenEmojiPicker(false);
74+
};
2375

2476
return (
25-
<Text
26-
{...textProps}
27-
$size={$size}
28-
$variation={$variation}
29-
$weight={$weight}
30-
aria-hidden="true"
31-
aria-label={t('Document emoji icon')}
32-
>
33-
{emoji}
34-
</Text>
77+
<>
78+
<Box ref={iconRef} onClick={toggleEmojiPicker} $position="relative">
79+
{!emoji ? (
80+
defaultIcon
81+
) : (
82+
<Icon
83+
{...textProps}
84+
iconName={emoji}
85+
$size={$size}
86+
$variation={$variation}
87+
$weight={$weight}
88+
aria-hidden="true"
89+
aria-label={t('Document emoji icon')}
90+
>
91+
{emoji}
92+
</Icon>
93+
)}
94+
</Box>
95+
{openEmojiPicker &&
96+
createPortal(
97+
<div
98+
style={{
99+
position: 'absolute',
100+
top: pickerPosition.top,
101+
left: pickerPosition.left,
102+
zIndex: 1000,
103+
}}
104+
>
105+
<EmojiPicker
106+
emojiData={emojidata}
107+
onEmojiSelect={handleEmojiSelect}
108+
onClickOutside={handleClickOutside}
109+
/>
110+
</div>,
111+
document.body,
112+
)}
113+
</>
35114
);
36115
};

src/frontend/apps/impress/src/features/docs/doc-management/components/SimpleDocItem.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export const SimpleDocItem = ({
7272
/>
7373
}
7474
$size="25px"
75+
docId={doc.id}
7576
/>
7677
)}
7778
</Box>

0 commit comments

Comments
 (0)