Skip to content

Commit 71f52ca

Browse files
committed
✨(frontend) add an EmojiPicker in the document tree
As discussed here suitenumerique#1358 (comment)
1 parent 2890922 commit 71f52ca

File tree

8 files changed

+439
-62
lines changed

8 files changed

+439
-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: 99 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
1-
import { Text, TextType } from '@/components';
1+
import React from 'react';
2+
import { createPortal } from 'react-dom';
3+
import { useTranslation } from 'react-i18next';
4+
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';
210

311
type DocIconProps = TextType & {
412
emoji?: string | null;
13+
emojiPicker?: boolean;
514
defaultIcon: React.ReactNode;
15+
docId?: string;
16+
title?: string;
17+
onEmojiUpdate?: (emoji: string) => void;
618
};
719

820
export const DocIcon = ({
@@ -11,22 +23,95 @@ export const DocIcon = ({
1123
$size = 'sm',
1224
$variation = '1000',
1325
$weight = '400',
26+
emojiPicker = false,
27+
docId,
28+
title,
29+
onEmojiUpdate,
1430
...textProps
1531
}: DocIconProps) => {
16-
if (!emoji) {
17-
return <>{defaultIcon}</>;
18-
}
32+
33+
const { t } = useTranslation();
34+
const { updateDocEmoji } = useDocTitleUpdate();
35+
36+
const iconRef = React.useRef<HTMLDivElement>(null);
37+
38+
const [openEmojiPicker, setOpenEmojiPicker] = React.useState<boolean>(false);
39+
const [pickerPosition, setPickerPosition] = React.useState<{
40+
top: number;
41+
left: number;
42+
}>({ top: 0, left: 0 });
43+
44+
const toggleEmojiPicker = (e: React.MouseEvent) => {
45+
if (emojiPicker) {
46+
e.stopPropagation();
47+
e.preventDefault();
48+
49+
if (!openEmojiPicker && iconRef.current) {
50+
const rect = iconRef.current.getBoundingClientRect();
51+
setPickerPosition({
52+
top: rect.bottom + window.scrollY + 8,
53+
left: rect.left + window.scrollX,
54+
});
55+
}
56+
57+
setOpenEmojiPicker(!openEmojiPicker);
58+
}
59+
};
60+
61+
const handleEmojiSelect = ({ native }: { native: string }) => {
62+
setOpenEmojiPicker(false);
63+
64+
// Update document emoji if docId is provided
65+
if (docId && title !== undefined) {
66+
updateDocEmoji(docId, title ?? '', native);
67+
}
68+
69+
// Call the optional callback
70+
onEmojiUpdate?.(native);
71+
};
72+
73+
const handleClickOutside = () => {
74+
setOpenEmojiPicker(false);
75+
};
1976

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

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
@@ -75,6 +75,7 @@ export const SimpleDocItem = ({
7575
/>
7676
}
7777
$size="25px"
78+
docId={doc.id}
7879
/>
7980
)}
8081
</Box>

0 commit comments

Comments
 (0)