(frontend) add "Add Emoji" button to doc options menu

- Add "Add Emoji" button to doc options menu
- Remove default emoji when none selected
- Improve doc options styling
This commit is contained in:
Anthony LC
2025-11-05 10:14:10 +01:00
parent f9ff578c6b
commit 66f83db0e5
4 changed files with 102 additions and 70 deletions

View File

@@ -68,9 +68,18 @@ test.describe('Doc Header', () => {
await createDoc(page, 'doc-update-emoji', browserName, 1);
const emojiPicker = page.locator('.--docs--doc-title').getByRole('button');
const optionMenu = page.getByLabel('Open the document options');
const addEmojiMenuItem = page.getByRole('menuitem', { name: 'Add emoji' });
const removeEmojiMenuItem = page.getByRole('menuitem', {
name: 'Remove emoji',
});
// Top parent should not have emoji picker
await expect(emojiPicker).toBeHidden();
await optionMenu.click();
await expect(addEmojiMenuItem).toBeHidden();
await expect(removeEmojiMenuItem).toBeHidden();
await page.keyboard.press('Escape');
const { name: docChild } = await createRootSubPage(
page,
@@ -80,13 +89,23 @@ test.describe('Doc Header', () => {
await verifyDocName(page, docChild);
await expect(emojiPicker).toBeVisible();
// Emoji picker should be hidden initially
await expect(emojiPicker).toBeHidden();
// Add emoji
await optionMenu.click();
await expect(removeEmojiMenuItem).toBeHidden();
await addEmojiMenuItem.click();
await expect(emojiPicker).toHaveText('📄');
// Change emoji
await emojiPicker.click({
delay: 100,
});
await page.getByRole('button', { name: '😀' }).first().click();
await expect(emojiPicker).toHaveText('😀');
// Update title
const docTitle = page.getByRole('textbox', { name: 'Document title' });
await docTitle.fill('Hello Emoji World');
await docTitle.blur();
@@ -95,6 +114,12 @@ test.describe('Doc Header', () => {
// Check the tree
const row = await getTreeRow(page, 'Hello Emoji World');
await expect(row.getByText('😀')).toBeVisible();
// Remove emoji
await optionMenu.click();
await expect(addEmojiMenuItem).toBeHidden();
await removeEmojiMenuItem.click();
await expect(emojiPicker).toBeHidden();
});
test('it deletes the doc', async ({ page, browserName }) => {

View File

@@ -352,7 +352,7 @@ test.describe('Doc Tree', () => {
await page.getByRole('menuitem', { name: 'Remove emoji' }).click();
await expect(row.getByText('😀')).toBeHidden();
await expect(titleEmojiPicker).not.toHaveText('😀');
await expect(titleEmojiPicker).toBeHidden();
});
});

View File

@@ -55,8 +55,16 @@ const DocTitleEmojiPicker = ({ doc }: DocTitleProps) => {
const { colorsTokens } = useCunninghamTheme();
const { emoji } = getEmojiAndTitle(doc.title ?? '');
if (!emoji) {
return null;
}
return (
<Tooltip content={t('Document emoji')} aria-hidden={true} placement="top">
<Tooltip
content={t('Edit document emoji')}
aria-hidden={true}
placement="top"
>
<Box
$css={css`
padding: 4px;
@@ -70,11 +78,17 @@ const DocTitleEmojiPicker = ({ doc }: DocTitleProps) => {
`}
>
<DocIcon
buttonProps={{
$width: '32px',
$height: '32px',
$justify: 'space-between',
$align: 'center',
}}
withEmojiPicker={doc.abilities.partial_update}
docId={doc.id}
title={doc.title}
emoji={emoji}
$size="25px"
$size="23px"
defaultIcon={
<SimpleFileIcon
width="25px"
@@ -94,7 +108,6 @@ const DocTitleInput = ({ doc }: DocTitleProps) => {
const { isDesktop } = useResponsiveStore();
const { t } = useTranslation();
const { colorsTokens } = useCunninghamTheme();
const { spacingsTokens } = useCunninghamTheme();
const { isTopRoot } = useDocUtils(doc);
const { untitledDocument } = useTrans();
const { emoji, titleWithoutEmoji } = getEmojiAndTitle(doc.title ?? '');
@@ -139,19 +152,9 @@ const DocTitleInput = ({ doc }: DocTitleProps) => {
className="--docs--doc-title"
$direction="row"
$align="center"
$gap={spacingsTokens['xs']}
$gap="4px"
$minHeight="40px"
>
{isTopRoot && (
<SimpleFileIcon
width="25px"
height="25px"
aria-hidden="true"
aria-label={t('Simple document icon')}
color={colorsTokens['primary-500']}
style={{ flexShrink: '0' }}
/>
)}
{!isTopRoot && <DocTitleEmojiPicker doc={doc} />}
<Tooltip content={t('Rename')} aria-hidden={true} placement="top">

View File

@@ -60,7 +60,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
const selectHistoryModal = useModal();
const modalShare = useModal();
const { isSmallMobile, isDesktop } = useResponsiveStore();
const { isSmallMobile, isMobile } = useResponsiveStore();
const copyDocLink = useCopyDocLink(doc.id);
const { mutate: duplicateDoc } = useDuplicateDoc({
onSuccess: (data) => {
@@ -90,28 +90,20 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
const { updateDocEmoji } = useDocTitleUpdate();
const options: DropdownMenuOption[] = [
...(isSmallMobile
? [
{
label: t('Share'),
icon: 'group',
callback: modalShare.open,
},
{
label: t('Export'),
icon: 'download',
callback: () => {
setIsModalExportOpen(true);
},
show: !!ModalExport,
},
{
label: t('Copy link'),
icon: 'add_link',
callback: copyDocLink,
},
]
: []),
{
label: t('Share'),
icon: 'group',
callback: modalShare.open,
show: isSmallMobile,
},
{
label: t('Export'),
icon: 'download',
callback: () => {
setIsModalExportOpen(true);
},
show: !!ModalExport && isSmallMobile,
},
{
label: doc.is_favorite ? t('Unpin') : t('Pin'),
icon: 'push_pin',
@@ -124,17 +116,6 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
},
testId: `docs-actions-${doc.is_favorite ? 'unpin' : 'pin'}-${doc.id}`,
},
...(emoji && doc.abilities.partial_update && !isTopRoot
? [
{
label: t('Remove emoji'),
icon: 'emoji_emotions',
callback: () => {
updateDocEmoji(doc.id, doc.title ?? '', '');
},
},
]
: []),
{
label: t('Version history'),
icon: 'history',
@@ -142,7 +123,31 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
callback: () => {
selectHistoryModal.open();
},
show: isDesktop,
show: !isMobile,
showSeparator: isTopRoot ? true : false,
},
{
label: t('Remove emoji'),
icon: 'emoji_emotions',
callback: () => {
updateDocEmoji(doc.id, doc.title ?? '', '');
},
showSeparator: true,
show: !!emoji && doc.abilities.partial_update && !isTopRoot,
},
{
label: t('Add emoji'),
icon: 'emoji_emotions',
callback: () => {
updateDocEmoji(doc.id, doc.title ?? '', '📄');
},
showSeparator: true,
show: !emoji && doc.abilities.partial_update && !isTopRoot,
},
{
label: t('Copy link'),
icon: 'add_link',
callback: copyDocLink,
},
{
label: t('Copy as {{format}}', { format: 'Markdown' }),
@@ -158,6 +163,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
void copyCurrentEditorToClipboard('html');
},
show: isFeatureFlagActivated('CopyAsHTML'),
showSeparator: true,
},
{
label: t('Duplicate'),
@@ -170,6 +176,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
canSave: doc.abilities.partial_update,
});
},
showSeparator: true,
},
{
label: isChild ? t('Delete sub-document') : t('Delete document'),
@@ -224,25 +231,22 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
aria-label={t('Export the document')}
/>
)}
<DropdownMenu options={options} label={t('Open the document options')}>
<IconOptions
aria-hidden="true"
isHorizontal
$theme="primary"
$padding={{ all: 'xs' }}
$css={css`
border-radius: 4px;
&:hover {
background-color: ${colorsTokens['greyscale-100']};
}
${isSmallMobile
? css`
padding: 10px;
border: 1px solid ${colorsTokens['greyscale-300']};
`
: ''}
`}
/>
<DropdownMenu
options={options}
label={t('Open the document options')}
buttonCss={css`
padding: ${spacingsTokens['xs']};
&:hover {
background-color: ${colorsTokens['greyscale-100']};
}
${isSmallMobile
? css`
border: 1px solid ${colorsTokens['greyscale-300']};
`
: ''}
`}
>
<IconOptions aria-hidden="true" isHorizontal $theme="primary" />
</DropdownMenu>
</Box>