Skip to content

Commit 08f3cea

Browse files
olaurendeauAntoLC
authored andcommitted
✨(frontend) add EmojiPicker in DocumentTitle
We can now add emojis to the document title using the EmojiPicker component.
1 parent b1d033e commit 08f3cea

File tree

9 files changed

+205
-58
lines changed

9 files changed

+205
-58
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to
99
### Added
1010

1111
- ✨(frontend) create skeleton component for DocEditor #1491
12+
- ✨(frontend) add an EmojiPicker in the document tree and title #1381
1213

1314
### Changed
1415

@@ -99,6 +100,9 @@ and this project adheres to
99100
### Added
100101

101102
- ✨(api) add API route to fetch document content #1206
103+
- ✨(frontend) doc emojis improvements #1381
104+
- add an EmojiPicker in the document tree and document title
105+
- remove emoji buttons in menus
102106

103107
### Changed
104108

@@ -112,6 +116,8 @@ and this project adheres to
112116
- ✨unify tab focus style for better visual consistency #1341
113117
- ♿hide decorative icons, label menus, avoid accessible name… #1362
114118
- ♻️(tilt) use helm dev-backend chart
119+
- 🩹(frontend) on main pages do not display leading emoji as page icon #1381
120+
- 🩹(frontend) handle properly emojis in interlinking #1381
115121

116122
### Removed
117123

src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,36 @@ test.describe('Doc Header', () => {
6565
page,
6666
browserName,
6767
}) => {
68-
await createDoc(page, 'doc-update', browserName, 1);
68+
await createDoc(page, 'doc-update-emoji', browserName, 1);
69+
70+
const emojiPicker = page.locator('.--docs--doc-title').getByRole('button');
71+
72+
// Top parent should not have emoji picker
73+
await expect(emojiPicker).toBeHidden();
74+
75+
const { name: docChild } = await createRootSubPage(
76+
page,
77+
browserName,
78+
'doc-update-emoji-child',
79+
);
80+
81+
await verifyDocName(page, docChild);
82+
83+
await expect(emojiPicker).toBeVisible();
84+
await emojiPicker.click({
85+
delay: 100,
86+
});
87+
await page.getByRole('button', { name: '😀' }).first().click();
88+
await expect(emojiPicker).toHaveText('😀');
89+
6990
const docTitle = page.getByRole('textbox', { name: 'Document title' });
70-
await expect(docTitle).toBeVisible();
71-
await docTitle.fill('👍 Hello Emoji World');
91+
await docTitle.fill('Hello Emoji World');
7292
await docTitle.blur();
73-
await verifyDocName(page, '👍 Hello Emoji World');
93+
await verifyDocName(page, 'Hello Emoji World');
7494

7595
// Check the tree
7696
const row = await getTreeRow(page, 'Hello Emoji World');
77-
await expect(row.getByText('👍')).toBeVisible();
97+
await expect(row.getByText('😀')).toBeVisible();
7898
});
7999

80100
test('it deletes the doc', async ({ page, browserName }) => {

src/frontend/apps/e2e/__tests__/app-impress/doc-tree.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -340,19 +340,19 @@ test.describe('Doc Tree', () => {
340340

341341
// Verify the emoji is updated in the tree and in the document title
342342
await expect(row.getByText('😀')).toBeVisible();
343-
await expect(
344-
page.getByRole('textbox', { name: 'Document title' }),
345-
).toContainText('😀');
343+
344+
const titleEmojiPicker = page
345+
.locator('.--docs--doc-title')
346+
.getByRole('button');
347+
await expect(titleEmojiPicker).toHaveText('😀');
346348

347349
// Now remove the emoji using the new action
348350
await row.hover();
349351
await menu.click();
350352
await page.getByRole('menuitem', { name: 'Remove emoji' }).click();
351353

352354
await expect(row.getByText('😀')).toBeHidden();
353-
await expect(
354-
page.getByRole('textbox', { name: 'Document title' }),
355-
).not.toContainText('😀');
355+
await expect(titleEmojiPicker).not.toHaveText('😀');
356356
});
357357
});
358358

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>
22+
<Box $position="absolute" $zIndex={1000} $margin="2rem 0 0 0">
2323
<Picker
2424
data={emojiData}
2525
locale={i18n.resolvedLanguage}

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

Lines changed: 118 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@ import { Box, Text } from '@/components';
77
import { useCunninghamTheme } from '@/cunningham';
88
import {
99
Doc,
10+
DocIcon,
11+
getEmojiAndTitle,
1012
useDocStore,
1113
useDocTitleUpdate,
14+
useDocUtils,
1215
useIsCollaborativeEditable,
1316
useTrans,
1417
} from '@/docs/doc-management';
18+
import SimpleFileIcon from '@/features/docs/doc-management/assets/simple-document.svg';
1519
import { useResponsiveStore } from '@/stores';
1620

1721
interface DocTitleProps {
@@ -46,22 +50,77 @@ export const DocTitleText = () => {
4650
);
4751
};
4852

53+
const DocTitleEmojiPicker = ({ doc }: DocTitleProps) => {
54+
const { t } = useTranslation();
55+
const { colorsTokens } = useCunninghamTheme();
56+
const { emoji } = getEmojiAndTitle(doc.title ?? '');
57+
58+
return (
59+
<Tooltip content={t('Document emoji')} aria-hidden={true} placement="top">
60+
<Box
61+
$css={css`
62+
padding: 4px;
63+
padding-top: 3px;
64+
cursor: pointer;
65+
&:hover {
66+
background-color: ${colorsTokens['greyscale-100']};
67+
border-radius: 4px;
68+
}
69+
transition: background-color 0.2s ease-in-out;
70+
`}
71+
>
72+
<DocIcon
73+
withEmojiPicker={doc.abilities.partial_update}
74+
docId={doc.id}
75+
title={doc.title}
76+
emoji={emoji}
77+
$size="25px"
78+
defaultIcon={
79+
<SimpleFileIcon
80+
width="25px"
81+
height="25px"
82+
aria-hidden="true"
83+
aria-label={t('Simple document icon')}
84+
color={colorsTokens['primary-500']}
85+
/>
86+
}
87+
/>
88+
</Box>
89+
</Tooltip>
90+
);
91+
};
92+
4993
const DocTitleInput = ({ doc }: DocTitleProps) => {
5094
const { isDesktop } = useResponsiveStore();
5195
const { t } = useTranslation();
5296
const { colorsTokens } = useCunninghamTheme();
53-
const [titleDisplay, setTitleDisplay] = useState(doc.title);
54-
97+
const { spacingsTokens } = useCunninghamTheme();
98+
const { isTopRoot } = useDocUtils(doc);
5599
const { untitledDocument } = useTrans();
100+
const { emoji, titleWithoutEmoji } = getEmojiAndTitle(doc.title ?? '');
101+
const [titleDisplay, setTitleDisplay] = useState(
102+
isTopRoot ? doc.title : titleWithoutEmoji,
103+
);
56104

57105
const { updateDocTitle } = useDocTitleUpdate();
58106

59107
const handleTitleSubmit = useCallback(
60108
(inputText: string) => {
61-
const sanitizedTitle = updateDocTitle(doc, inputText.trim());
62-
setTitleDisplay(sanitizedTitle);
109+
if (isTopRoot) {
110+
const sanitizedTitle = updateDocTitle(doc, inputText);
111+
setTitleDisplay(sanitizedTitle);
112+
} else {
113+
const sanitizedTitle = updateDocTitle(
114+
doc,
115+
emoji ? `${emoji} ${inputText}` : inputText,
116+
);
117+
const { titleWithoutEmoji: sanitizedTitleWithoutEmoji } =
118+
getEmojiAndTitle(sanitizedTitle);
119+
120+
setTitleDisplay(sanitizedTitleWithoutEmoji);
121+
}
63122
},
64-
[doc, updateDocTitle],
123+
[updateDocTitle, doc, emoji, isTopRoot],
65124
);
66125

67126
const handleKeyDown = (e: React.KeyboardEvent) => {
@@ -72,43 +131,62 @@ const DocTitleInput = ({ doc }: DocTitleProps) => {
72131
};
73132

74133
useEffect(() => {
75-
setTitleDisplay(doc.title);
76-
}, [doc]);
134+
setTitleDisplay(isTopRoot ? doc.title : titleWithoutEmoji);
135+
}, [doc.title, isTopRoot, titleWithoutEmoji]);
77136

78137
return (
79-
<Tooltip content={t('Rename')} aria-hidden={true} placement="top">
80-
<Box
81-
as="span"
82-
role="textbox"
83-
className="--docs--doc-title-input"
84-
contentEditable
85-
defaultValue={titleDisplay || undefined}
86-
onKeyDownCapture={handleKeyDown}
87-
suppressContentEditableWarning={true}
88-
aria-label={`${t('Document title')}`}
89-
aria-multiline={false}
90-
onBlurCapture={(event) =>
91-
handleTitleSubmit(event.target.textContent || '')
92-
}
93-
$color={colorsTokens['greyscale-1000']}
94-
$minHeight="40px"
95-
$padding={{ right: 'big' }}
96-
$css={css`
97-
&[contenteditable='true']:empty:not(:focus):before {
98-
content: '${untitledDocument}';
99-
color: grey;
100-
pointer-events: none;
101-
font-style: italic;
138+
<Box
139+
className="--docs--doc-title"
140+
$direction="row"
141+
$align="center"
142+
$gap={spacingsTokens['xs']}
143+
$minHeight="40px"
144+
>
145+
{isTopRoot && (
146+
<SimpleFileIcon
147+
width="25px"
148+
height="25px"
149+
aria-hidden="true"
150+
aria-label={t('Simple document icon')}
151+
color={colorsTokens['primary-500']}
152+
style={{ flexShrink: '0' }}
153+
/>
154+
)}
155+
{!isTopRoot && <DocTitleEmojiPicker doc={doc} />}
156+
157+
<Tooltip content={t('Rename')} aria-hidden={true} placement="top">
158+
<Box
159+
as="span"
160+
role="textbox"
161+
className="--docs--doc-title-input"
162+
contentEditable
163+
defaultValue={titleDisplay || undefined}
164+
onKeyDownCapture={handleKeyDown}
165+
suppressContentEditableWarning={true}
166+
aria-label={`${t('Document title')}`}
167+
aria-multiline={false}
168+
onBlurCapture={(event) =>
169+
handleTitleSubmit(event.target.textContent || '')
102170
}
103-
font-size: ${isDesktop
104-
? css`var(--c--theme--font--sizes--h2)`
105-
: css`var(--c--theme--font--sizes--sm)`};
106-
font-weight: 700;
107-
outline: none;
108-
`}
109-
>
110-
{titleDisplay}
111-
</Box>
112-
</Tooltip>
171+
$color={colorsTokens['greyscale-1000']}
172+
$padding={{ right: 'big' }}
173+
$css={css`
174+
&[contenteditable='true']:empty:not(:focus):before {
175+
content: '${untitledDocument}';
176+
color: grey;
177+
pointer-events: none;
178+
font-style: italic;
179+
}
180+
font-size: ${isDesktop
181+
? css`var(--c--theme--font--sizes--h2)`
182+
: css`var(--c--theme--font--sizes--sm)`};
183+
font-weight: 700;
184+
outline: none;
185+
`}
186+
>
187+
{titleDisplay}
188+
</Box>
189+
</Tooltip>
190+
</Box>
113191
);
114192
};

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@ import {
2020
KEY_DOC,
2121
KEY_LIST_DOC,
2222
ModalRemoveDoc,
23+
getEmojiAndTitle,
2324
useCopyDocLink,
2425
useCreateFavoriteDoc,
2526
useDeleteFavoriteDoc,
27+
useDocTitleUpdate,
2628
useDocUtils,
2729
useDuplicateDoc,
2830
} from '@/docs/doc-management';
@@ -49,7 +51,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
4951
const treeContext = useTreeContext<Doc>();
5052
const queryClient = useQueryClient();
5153
const router = useRouter();
52-
const { isChild } = useDocUtils(doc);
54+
const { isChild, isTopRoot } = useDocUtils(doc);
5355

5456
const { spacingsTokens, colorsTokens } = useCunninghamTheme();
5557

@@ -83,6 +85,10 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
8385
});
8486
}, [selectHistoryModal.isOpen, queryClient]);
8587

88+
// Emoji Management
89+
const { emoji } = getEmojiAndTitle(doc.title ?? '');
90+
const { updateDocEmoji } = useDocTitleUpdate();
91+
8692
const options: DropdownMenuOption[] = [
8793
...(isSmallMobile
8894
? [
@@ -118,6 +124,17 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
118124
},
119125
testId: `docs-actions-${doc.is_favorite ? 'unpin' : 'pin'}-${doc.id}`,
120126
},
127+
...(emoji && doc.abilities.partial_update && !isTopRoot
128+
? [
129+
{
130+
label: t('Remove emoji'),
131+
icon: 'emoji_emotions',
132+
callback: () => {
133+
updateDocEmoji(doc.id, doc.title ?? '', '');
134+
},
135+
},
136+
]
137+
: []),
121138
{
122139
label: t('Version history'),
123140
icon: 'history',

src/frontend/apps/impress/src/features/docs/doc-management/assets/simple-document.svg

Lines changed: 0 additions & 2 deletions
Loading

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ export const SimpleDocItem = ({
7373
/>
7474
) : (
7575
<SimpleFileIcon
76+
width="32px"
77+
height="32px"
7678
aria-hidden="true"
7779
data-testid="doc-simple-icon"
7880
color={colorsTokens['primary-500']}

0 commit comments

Comments
 (0)